/*
Este programa implementa o algoritmo de Gauss-Legendre, como gauss-legendre.c.
Porém, este faz uso da biblioteca Pthreads para parelelizar parte do código.
Especificamente, o loop principal é dividido em 4 threads: uma para o cálculo
de a, outra para b, outra para t, e outra para p + sincronização.
*/

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <gmp.h>
#define NUM_THREADS 4

//Declaração das variáveis utilizadas nos cálculos
mpf_t a, b, t, p, pi;
mpf_t ap, bp, tp, pp;

//Variável que determina o a convergência e variável para contagem das threads
//finalizadas a cada iteração
int end, loop_count, i;

//Semáforos e mutex
sem_t a_sem, t1_loop, t2_loop, t3_loop, t4_loop;
pthread_mutex_t loop_count_mutex; 

void* thread_1(void* dummy)
{
   while(1)
   {
      //Espera pela cópia dos valores da iteração atual 
      sem_wait(&t1_loop);
      if(end)
         pthread_exit(NULL);
     
      //Cálculo de a 
      mpf_add(a, ap, bp);
      mpf_div_ui(a, a, 2);
      
      //Aviso de que o valor atual de a pode ser utilizado
      sem_post(&a_sem);
      
      //Indica o fim da iteração atual
      pthread_mutex_lock(&loop_count_mutex);
      loop_count++;
      if(loop_count == 3)
         sem_post(&t4_loop);
      pthread_mutex_unlock(&loop_count_mutex);
   }
}

void* thread_2(void* dummy)
{
   while(1)
   {
      //Espera pela cópia dos valores da iteração atual 
      sem_wait(&t2_loop);
      if(end)
         pthread_exit(NULL);

      //Cálculo de b  
      mpf_mul(b, ap, bp);
      mpf_sqrt(b, b);

      //Indica o fim da iteração atual 
      pthread_mutex_lock(&loop_count_mutex);
      loop_count++;
      if(loop_count == 3)
         sem_post(&t4_loop);
      pthread_mutex_unlock(&loop_count_mutex);
   }
}

void* thread_3(void* dummy)
{
   while(1)
   {
      //Espera pela cópia dos valores da iteração atual 
      sem_wait(&t3_loop);
      if(end)
         pthread_exit(NULL);
      
      //Espera pelo valor de a
      sem_wait(&a_sem);
      
      //Cálculo de t
      mpf_sub(t, ap, a);
      mpf_pow_ui(t, t, 2);
      mpf_mul(t, pp, t);
      mpf_sub(t, tp, t);
      
      //Indica o fim da iteração atual 
      pthread_mutex_lock(&loop_count_mutex);
      loop_count++;
      if(loop_count == 3)
         sem_post(&t4_loop);
      pthread_mutex_unlock(&loop_count_mutex);
   }
}

void* thread_4(void* dummy)
{
   while(1)
   {
      //Espera pelo término da iteração anterior nas outras threads
      sem_wait(&t4_loop);
      
      //Finalizar se convergir, e avisar demais threads
      if(mpf_cmp(a, ap) == 0)
      {
         end = 1;
         sem_post(&t1_loop);
         sem_post(&t2_loop);
         sem_post(&t3_loop);
         pthread_exit(NULL);
      }
      
      loop_count = 0;
      
      //Salva os valores atuais da sequência
      mpf_set(ap, a);
      mpf_set(bp, b);
      
      //Thread 1 e 2 podem executar
      sem_post(&t1_loop);
      sem_post(&t2_loop);
      mpf_set(pp, p);
      mpf_set(tp, t);
      
      //Thread 3 pode executar
      sem_post(&t3_loop);
      
      //Cálculo de p
      mpf_mul_ui(p, pp, 2);
      
      //Incrementa o número de iterações
      i++;
   }
}


int main(int argc, const char* argv[])
{
   //Declaração de variáveis para abertura de arquivo e medida de tempo
   FILE *fp;
   char filename[50];
   struct timeval ti, tf;
   long int sec, usec;
   
   //Início da contagem de tempo
   gettimeofday(&ti, NULL);
   
   //Declaração das threads e variáveis auxiliares
   pthread_t threads[NUM_THREADS];
   int precision, rc, th;
   void *status;

   //Inicialização de variáveis com a precisão especificada
   precision = atoi(argv[1]);
   mpf_set_default_prec(precision);
   mpf_init(a);
   mpf_init(b);
   mpf_init(t);
   mpf_init(p);
   mpf_init(pi);
   mpf_init(ap);
   mpf_init(bp);
   mpf_init(tp);
   mpf_init(pp);
   
   //Valores iniciais da sequência
   mpf_set_ui(a, 1);
   mpf_sqrt_ui(b, 2);
   mpf_ui_div(b, 1, b);
   mpf_set_ui(t, 1);
   mpf_div_ui(t, t, 4);
   mpf_set_ui(p, 1);
   
   //Inicialização dos semáforos e mutex
   pthread_mutex_init(&loop_count_mutex, NULL);
   sem_init(&a_sem, 0, 0);
   sem_init(&t1_loop, 0, 0);
   sem_init(&t2_loop, 0, 0);
   sem_init(&t3_loop, 0, 0);
   sem_init(&t4_loop, 0, 1);
   end = 0;
   
   //Criação das threads
   rc = pthread_create(&threads[3], NULL, thread_4, NULL);
   if(rc)
   {
      printf("ERRO; codigo  da  pthread_create() e %d\n", rc);
      exit(-1);
   }
   rc = pthread_create(&threads[0], NULL, thread_1, NULL);
   if(rc)
   {
      printf("ERRO; codigo  da  pthread_create() e %d\n", rc);
      exit(-1);
   }
   rc = pthread_create(&threads[1], NULL, thread_2, NULL);
   if(rc)
   {
      printf("ERRO; codigo  da  pthread_create() e %d\n", rc);
      exit(-1);
   }
   rc = pthread_create(&threads[2], NULL, thread_3, NULL);
   if(rc)
   {
      printf("ERRO; codigo  da  pthread_create() e %d\n", rc);
      exit(-1);
   }
   
   //Aguarda as threads finalizarem
   for(th = 0; th < NUM_THREADS; th++)
      pthread_join(threads[th], &status);

   //Cálculo final de pi (pi ~= (a_n + b_n)^2/(4t_n))
   mpf_add(a, a, b);
   mpf_pow_ui(a, a, 2);
   mpf_mul_ui(pi, t, 4);
   mpf_div(pi, a, pi);
   
   
   //Término da contagem de tempo
   gettimeofday(&tf, NULL);
   sec = tf.tv_sec - ti.tv_sec;
   if(tf.tv_usec < ti.tv_usec)
      sec--;
   usec = (tf.tv_usec - ti.tv_usec + 1000000) % 1000000;
   printf("Tempo decorrido: %ld.%06ld s. Iterações: %d\n", sec, usec, i);
   
   //Salva o resultado para arquivo
   sprintf(filename, "pi/gauss-legendre-par-%d", precision);
   fp = fopen(filename, "w");
   gmp_fprintf(fp, "%.Ff\n", pi);
   fclose(fp);

   return 0;
}
